#include "obj_parser.h"
#include "solid_loader.h"
#include <vector>
#include <algorithm>
#include <sstream>
#include <fstream>
#include <map>
#include <cassert>
using namespace std;

namespace gemren
{
	typedef solid_loader::solid_data solid_data;
	typedef bool (*line_parse_f)(istream&, solid_data&);
	typedef map<string, line_parse_f> worker_map;
	void replace_slash(char& c)
	{
		if(c == '/') c=' '; 
	}
	
	bool parse_triangle_info(istream& is, size_t& v, size_t& t, size_t& n, int& datatype) 
	{ //parses info about one vertex of a triangle like 42//85
		if(is.eof()) return false;
		std::string atom;
		is>>atom;
		if(atom == "") return false;
		for_each(atom.begin(), atom.end(), replace_slash);
		stringstream ss(atom);
		int ndt = 1;
		ss>>noskipws>>v;
		if(!ss.eof())
		{
			char c;
			ss>>c;
		//	assert(c==' ');
			c = ss.peek();
			if(c != ' ')
			{
				ndt |= 2;
				ss>>t;
			}
			if(!ss.eof())
			{	
				ss>>c;
				//assert(c==' ');
				ss>>n;
				ndt |= 4;
			}
		}
	//	assert(datatype ==0 || datatype ==ndt);
		datatype = ndt;
		return true;
	}
	
	//workers for all commands like v ..., vn ...., f ....
	bool w_read_vertex(istream& linestr, solid_data& data)
	{ // "v"
		float x,y,z;
		linestr >> x >> y >> z;
		data.verts.push_back(data.transmat * coord(x,y,z));
		return true;
	}
	bool w_read_normal(istream& linestr, solid_data& data)
	{// "vn"
		float x,y,z;
		linestr >> x >> y >> z;
		data.norms.push_back(embree::normalize(data.transmat.transform_vector(coord(x,y,z)) ));
		return true;
	}
	bool w_read_texcoord(istream& linestr, solid_data& data)
	{// "vt"
		float x,y;
		linestr >> x >> y;
		data.texs.push_back( coord2d(x,y) );
		return true;
	}
	bool w_read_face(istream& linestr, solid_data& data)
	{// "f"
		int triangle_data_type=0;
		size_t u,v,w; 
		size_t nu,nv,nw;
		size_t tu,tv,tw;
		parse_triangle_info(linestr,u,tu,nu,triangle_data_type);
		parse_triangle_info(linestr,v,tv,nv,triangle_data_type);
		//if face has >3 vertices, it will be split into triangles with one mutual vertex
		while(parse_triangle_info(linestr,w,tw,nw,triangle_data_type)) 
		{	
			assert(triangle_data_type & 1);
			triangle tr(
				u-1, 
				v-1, 
				w-1,
				data.mat,
				0 //TODO real material
			);
			if(triangle_data_type & 4) 
				tr.set_normals(
					nu-1, 
					nv-1, 
					nw-1 
				);
			if(triangle_data_type & 2) 
				tr.set_texcoords(
					tu-1, 
					tv-1, 
					tw-1
				);
			tr.datatype = char(triangle_data_type);
			data.tris.push_back(tr );
			v = w;
			tv = tw;
			nv = nw;
		}
		return true;

	}
	bool w_parse_mtllib(istream& fnamestr, solid_data& data)
	{// "mtllib"
		string bsdfstr("bsdf");
		string filename;
		fnamestr>>filename;
		ifstream f(filename);
		material_ptr cur = NULL;
		string line, atom;
		while(!f.eof())
		{
			getline(f, line);
			stringstream linestr(line);
			atom.clear();
			linestr>>atom;
			if(linestr.good())
			{
				if(atom == "newmtl")
				{
					linestr>>atom;
					cur = new bsdf_material();
					cur->set_string(bsdfstr, "mtl");
					data.materials.insert(make_pair(atom,cur));
				}
				else
				{
					string val;
					getline(linestr, val);
					cur->set_string(atom, val);
				}

			}
		}
		return true;
	}
	bool w_switch_material(istream& namestr, solid_data& data)
	{
		string mat;
		namestr>>mat;
		material_cont::const_iterator it = data.materials.find(mat);
		if(/*!namestr.good() ||*/ it == data.materials.end())
			return true;//false;
		data.mat = it->second;
		return true;
	}
	bool w_switch_group(istream& namestr, solid_data& data)
	{
		string grp;
		namestr>>grp;
		map<string, string>::const_iterator it = data.groupmat.find(grp);
		if(/*!namestr.good() ||*/ it == data.groupmat.end())
			return true;//false;
		material_cont::const_iterator im = data.materials.find(it->second);
		if(/*!namestr.good() ||*/ im == data.materials.end())
			return true;//false;
		
		data.mat = im->second;
		return true;
	}
	
	void load_triangles(::size_t tid, void* ptr, ::size_t elt)//Scene& scene, const char filename[], const matrix4x4& transmat, const material_ptr mat)
	{ //reads an obj file
		vector<solid_data>* darray = (std::vector<solid_data>*)ptr;
		solid_data& src = darray->at(elt); //make a copy
		solid_data data(src.transmat, src.mat, src.filename, src.groupmat);
		std::ifstream file(data.filename);
		
		if(!file.good()) return;

		const material_ptr mat = data.mat;
		const matrix4x4& transmat = data.transmat;
		gemren::material_cont& mtl = data.materials;
		typedef embree::Vec3f coord;
		std::list<coord>& verts = data.verts,			  
						& norms = data.norms;
		std::list<coord2d>& texs = data.texs;
		std::list<triangle>& tris = data.tris; 
		
		std::string line, atom, subatom;
		worker_map workers;
		workers["v"]  =  w_read_vertex;
		workers["vn"] = w_read_normal;
		workers["vt"] = w_read_texcoord;
		workers["f"]  = w_read_face;
		workers["mtllib"] = w_parse_mtllib;
		workers["usemtl"] = w_switch_material;
		workers["g"] = w_switch_group;

		while(!file.eof()){
			getline(file, line);
			stringstream linestr(line);
			atom = "";
			linestr >> atom;
			if(atom.length()>=1) //not an empty line
			{
				worker_map::iterator w( workers.find(atom));
				if( w!= workers.end())
					if(!w->second(linestr, data)) assert(false);
			}
		}
	
		file.close();
		src.copy_data( data);
		
	}


}